package marubinotto.piggydb.impl.query;
import static marubinotto.util.CollectionUtils.pickRandomly;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import marubinotto.piggydb.impl.QueryUtils;
import marubinotto.piggydb.model.Filter;
import marubinotto.piggydb.model.Fragment;
import marubinotto.piggydb.model.RelatedTags;
import marubinotto.piggydb.model.TagRepository;
import marubinotto.piggydb.model.query.FragmentsByFilter;
import marubinotto.util.Assert;
import marubinotto.util.paging.Page;
import marubinotto.util.paging.PageImpl;
import marubinotto.util.paging.PageUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.RowMapper;
public final class H2FragmentsByFilter
extends H2FragmentsQueryBase implements FragmentsByFilter {
private static Log logger = LogFactory.getLog(H2FragmentsByFilter.class);
private Filter filter;
public void setFilter(Filter filter) {
this.filter = filter;
}
private String keywords;
public void setKeywords(String keywords) {
this.keywords = keywords;
}
protected void appendFromWhere(StringBuilder sql, List<Object> args) throws Exception {
// Do nothing
}
public List<Fragment> getAll() throws Exception {
return getByIds(getFilteredIds(true));
}
public Page<Fragment> getPage(int pageSize, int pageIndex) throws Exception {
List<Long> filteredIds = getFilteredIds(true);
if (filteredIds.isEmpty()) return PageUtils.empty(pageSize);
Page<Long> pagedIds = null;
if (getSortOption().shuffle) {
List<Long> picked = pickRandomly(filteredIds, new ArrayList<Long>(), pageSize);
pagedIds = new PageImpl<Long>(picked, pageSize, pageIndex, filteredIds.size());
}
else {
// Get ONLY the fragments in the page, which is why the IDs needs to be sorted
pagedIds = PageUtils.getPage(filteredIds, pageSize, pageIndex);
}
return new PageImpl<Fragment>(
getByIds(pagedIds),
pagedIds.getPageSize(),
pagedIds.getPageIndex(),
filteredIds.size());
}
public RelatedTags getRelatedTags() throws Exception {
Assert.Property.requireNotNull(filter, "filter");
RelatedTags relatedTags = new RelatedTags();
relatedTags.setFilter(this.filter);
List<Long> filteredIds = getFilteredIds(false);
if (filteredIds.isEmpty()) return relatedTags;
List<Page<Long>> pages =
PageUtils.splitToPages(filteredIds, COLLECT_RELATED_TAGS_AT_ONCE);
for (Page<Long> ids : pages) collectRelatedTags(ids, relatedTags);
return relatedTags;
}
private static final int COLLECT_RELATED_TAGS_AT_ONCE = 1000;
private void collectRelatedTags(List<Long> fragmentIds, final RelatedTags relatedTags) {
if (fragmentIds.isEmpty()) return;
StringBuilder sql = new StringBuilder();
sql.append("select tag_id, count(tag_id) from tagging");
sql.append(" where target_type = " + QueryUtils.TAGGING_TARGET_FRAGMENT);
sql.append(" and target_id in (");
for (int i = 0; i < fragmentIds.size(); i++) {
if (i > 0) sql.append(", ");
sql.append(fragmentIds.get(i));
}
sql.append(")");
sql.append(" group by tag_id");
logger.debug("collectRelatedTags: " + sql.toString());
getJdbcTemplate().query(sql.toString(), new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
relatedTags.add(rs.getLong(1), rs.getInt(2));
return null;
}
});
}
@SuppressWarnings("unchecked")
public List<Long> getFilteredIds(boolean sort) throws Exception {
Assert.Property.requireNotNull(filter, "filter");
StringBuilder sql = new StringBuilder();
List<Object> args = new ArrayList<Object>();
TagRepository tagRepository = getRepository().getTagRepository();
// Includes
List<Set<Long>> expandedTags = this.filter.getIncludes().expandEach(tagRepository);
if (expandedTags.size() > 0) {
for (Set<Long> tagTree : expandedTags) {
if (sql.length() > 0) {
sql.append(this.filter.isAnd() ? " intersect " : " union ");
}
appendSqlToSelectFragmentIdsTaggedWithAnyOf(sql, tagTree, sort);
}
}
else {
sql.append("select f.fragment_id");
if (sort) appendFieldForSort(sql, "f.");
sql.append(" from fragment as f");
}
// Keywords
if (StringUtils.isNotBlank(this.keywords)) {
sql.append(" intersect");
sql.append(" select f.fragment_id");
if (sort) appendFieldForSort(sql, "f.");
sql.append(" from fragment as f, FT_SEARCH_DATA(?, 0, 0) as ft");
sql.append(" where ft.TABLE ='FRAGMENT' and f.fragment_id = ft.KEYS[0]");
args.add(this.keywords);
}
// Excludes
Set<Long> excludes = this.filter.getExcludes().expandAll(tagRepository);
if (excludes.size() > 0) {
sql.append(" minus ");
appendSqlToSelectFragmentIdsTaggedWithAnyOf(sql, excludes, sort);
}
// Order
if (sort && !getSortOption().shuffle) appendSortOption(sql, "f.");
logger.debug("getFilteredIds: " + sql);
return getJdbcTemplate().query(sql.toString(), args.toArray(), new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getLong(1);
}
});
}
}